// SPDX-License-Identifier: Apache-2.0

package chisel3

import chisel3.experimental.{SourceInfo, UnlocatableSourceInfo}
import chisel3.internal.{Builder, HasId}
import chisel3.internal.firrtl.{GroupDefBegin, GroupDefEnd, Node}
import chisel3.util.simpleClassName
import scala.collection.mutable.LinkedHashSet

object group {

  object Convention {
    sealed trait Type

    private[chisel3] case object Root extends Type

    case object Bind extends Type
  }

  abstract class Declaration(val convention: Convention.Type)(implicit _parent: Declaration, _sourceInfo: SourceInfo) {
    self: Singleton =>

    protected final implicit val thiz: Declaration = this

    private[chisel3] def parent: Declaration = _parent

    private[chisel3] def sourceInfo: SourceInfo = _sourceInfo

    private[chisel3] def name: String = simpleClassName(this.getClass())
  }

  object Declaration {
    private[chisel3] case object Root extends Declaration(Convention.Root)(null, UnlocatableSourceInfo)
    implicit val rootDeclaration: Declaration = Root
  }

  private[chisel3] def addDeclarations(declaration: Declaration) = {
    var currentDeclaration: Declaration = declaration
    while (currentDeclaration != Declaration.Root && !Builder.groups.contains(currentDeclaration)) {
      val decl = currentDeclaration
      val parent = decl.parent

      Builder.groups += decl
      currentDeclaration = parent
    }
  }

  def apply[A](
    declaration: Declaration
  )(block:       => A
  )(
    implicit sourceInfo: SourceInfo
  ): Unit = {
    Builder.pushCommand(GroupDefBegin(sourceInfo, declaration))
    addDeclarations(declaration)
    require(
      Builder.groupStack.head == declaration.parent,
      s"nested group '${declaration.name}' must be wrapped in parent group '${declaration.parent.name}'"
    )
    Builder.groupStack = declaration :: Builder.groupStack
    block
    Builder.pushCommand(GroupDefEnd(sourceInfo))
    Builder.groupStack = Builder.groupStack.tail
  }

}
